import 'dart:convert';
import 'dart:typed_data';

import 'package:binary/binary.dart';
import 'package:inmotion_protocol/util/log.dart';

/*
 * Created by Jaren at 2021/8/17 16:13
 */
class ByteUtil {
  static String bytes2String(Uint8ClampedList list, [bool preventNonAsciiChar = true]) {
    int firstInvisibleAsciiChar = -1;
    for (int i = 0; i < list.length; i++) {
      if (list[i] < 0x20 || list[i] > 0x7e) {
        firstInvisibleAsciiChar = i;
        break;
      }
    }
    if (firstInvisibleAsciiChar > 0) list = list.sublist(0, firstInvisibleAsciiChar);
    try {
      return const Utf8Codec().decode(list);
    } catch (e) {
      LogUtils.e('Bytes to String failed!', e);
      return "Error";
    }
  }

  static List<String> bytes2StringList(Uint8ClampedList list) {
    List<String> strings = [];
    int lastInvalidByteIndex = 0;
    for (int i = 0; i < list.length; i++) {
      if (list[i] == 0 && i - 1 > lastInvalidByteIndex) {
        if (list[lastInvalidByteIndex] == 0) {
          lastInvalidByteIndex = i + 1;
          continue;
        }
        strings.add(const Utf8Codec().decode(list.sublist(lastInvalidByteIndex, i)));
        lastInvalidByteIndex = i + 1;
      }
    }
    return strings;
  }

  static String bytes2HexString(Uint8ClampedList list) {
    StringBuffer buffer = StringBuffer();
    for (int i in list) {
      String s = (i & 0xFF).toRadixString(16);
      s = s.length == 1 ? '0' + s : s;
      buffer.write(s);
    }
    return buffer.toString();
  }

  static String bytes2HexStringLe(Uint8ClampedList list, [int offset = 0, int length = 0]) {
    if (length == 0) length = list.length;
    StringBuffer buffer = StringBuffer();
    for (int i = 0; i < length; i++) {
      String s = (list[length - i - offset - 1] & 0xFF).toRadixString(16);
      s = s.length == 1 ? '0' + s : s;
      buffer.write(s);
    }
    return buffer.toString();
  }

  static String numberLe2HexString(int num) {
    int numberLength = 1;
    if (num <= 0xFFFF) {
      numberLength = 2;
    } else if (num <= 0xFFFFFF) {
      numberLength = 3;
    } else {
      numberLength = 4;
    }
    return bytes2HexString(num2BytesLe(num, numberLength));
  }

  static Uint8ClampedList hexString2Bytes(String hexString) {
    int len = hexString.length;
    Uint8ClampedList bytes = Uint8ClampedList(len ~/ 2);
    for (int i = 0; i < len; i += 2) {
      bytes[i ~/ 2] = hexStr2Byte(hexString.substring(i, i + 2)).value;
    }
    return bytes;
  }

  static void copy(Uint8ClampedList src, int srcIndex, Uint8ClampedList dest, int destIndex, int length) {
    for (int i = 0; i < length; i++) {
      dest[destIndex + i] = src[srcIndex + i];
    }
  }

  static Uint8ClampedList num2Bytes(int num, int length, [Uint8ClampedList? dest, int offset = 0]) {
    if (num < 0) num = ~num + 1;
    dest ??= Uint8ClampedList(length);
    for (int i = 0; i < length; i++) {
      dest[length + offset - 1 - i] = (num & 0xFF);
      num >>= 8; // 向右移8位
    }
    return dest;
  }

  static Uint8ClampedList num2BytesLe(int num, int length, [Uint8ClampedList? dest, int offset = 0]) {
    dest ??= Uint8ClampedList(length);
    for (int i = 0; i < length; i++) {
      dest[i + offset] = (num & 0xFF);
      num >>= 8; // 向右移8位
    }
    return dest;
  }

  static Uint8ClampedList str2Bytes(String str, [bool autoAdd0 = false]) {
    List<int> bytes = utf8.encode(str);
    if (autoAdd0) return buildBytes(Uint8ClampedList.fromList(bytes), Uint8ClampedList.fromList([0]));
    return Uint8ClampedList.fromList(bytes);
  }

  static Uint8ClampedList pwd2Bytes(String str, [bool autoAdd0 = false]) {
    List<int> bytes = [];
    for (int i = 0; i < str.length; i++) {
      int digit = int.parse(str[i]);
      bytes.add(digit);
    }
    if (autoAdd0) return buildBytes(Uint8ClampedList.fromList(bytes), Uint8ClampedList.fromList([0]));
    return Uint8ClampedList.fromList(bytes);
  }

  static Uint8 hexStr2Byte(String str, [int offset = 0, int length = 2]) {
    return toUint8(int.parse(str.substring(offset, offset + length), radix: 16));
  }

  static int byte2UInt(int i) {
    return ByteUtil.toUint8(i & 0xFF).value;
  }

  static String byte2Hex(int i) {
    i = i & 0xFF;
    return i < 0x10 ? "0" + i.toRadixString(16) : i.toRadixString(16);
  }

  static int bytes2IntLe(Uint8ClampedList bytes, [int src = 0, int length = 1]) {
    switch (length) {
      case 1:
        return bytes[src];
      case 2:
        var v = (bytes[src + 1] << 8) | bytes[src];
        return Int16(v > 0x7FFF ? -(0x10000 - v) : v).value;
      case 4:
        int v = (bytes[src + 3] << 24) | (bytes[src + 2] << 16) | (bytes[src + 1] << 8) | bytes[src];
        return Int32(v > 0x7FFFFFFF ? (-(~v & 0xFFFFFFFF) + 1) : v).value;
    }
    throw Exception('bytes2IntLe; Unsupported integer length ' + length.toString());
  }

  static int bytes2UIntLe(Uint8ClampedList bytes, [int src = 0, int length = 1]) {
    switch (length) {
      case 1:
        return ByteUtil.toUint8(bytes[src] & 0xFF).value;
      case 2:
        return ByteUtil.toUint16(((bytes[src + 1] << 8) | bytes[src]) & 0xFFFF).value;
      case 4:
        return ByteUtil.toUint32(((bytes[src + 3] << 24) | (bytes[src + 2] << 16) | (bytes[src + 1] << 8) | bytes[src]) & 0xFFFFFFFF).value;
    }
    throw Exception('bytes2IntLe; Unsupported integer length ' + length.toString());
  }

  static Int8 bytes2Int8Le(Uint8ClampedList bytes, [int src = 0]) {
    int v = bytes[src] & 0xFF;
    if (v > 0x7F) v = 0x7F - v;
    return Int8(v);
  }

  static Uint4 bytes2Uint4Le(Uint8ClampedList bytes, [int src = 0]) {
    return Uint4(bytes[src] & 0xFF);
  }

  static Uint8 bytes2Uint8Le(Uint8ClampedList bytes, [int src = 0]) {
    return Uint8(bytes[src] & 0xFF);
  }

  static Uint16 bytes2Uint16Le(Uint8ClampedList bytes, [int src = 0]) {
    return Uint16(((bytes[src + 1] << 8) | bytes[src]) & 0xFFFF);
  }

  static Int16 bytes2Int16Le(Uint8ClampedList bytes, [int src = 0]) {
    int v = ((bytes[src + 1] << 8) | bytes[src]) & 0xFFFF;
    if (v > 0x7FFF) v = v - 0x10000;
    return Int16(v);
  }

  static int bytes2Uint64Le(Uint8ClampedList bytes, [int src = 0]) {
    return ((bytes[src + 7] << 56) |
            (bytes[src + 6] << 48) |
            (bytes[src + 5] << 40) |
            (bytes[src + 4] << 32) |
            (bytes[src + 3] << 24) |
            (bytes[src + 2] << 16) |
            (bytes[src + 1] << 8) |
            bytes[src]) &
        0xFFFFFFFFFFFFFFFF;
  }

  static Uint32 bytes2Uint32Le(Uint8ClampedList bytes, [int src = 0]) {
    return Uint32(((bytes[src + 3] << 24) | (bytes[src + 2] << 16) | (bytes[src + 1] << 8) | bytes[src]) & 0xFFFFFFFF);
  }

  static Uint32 bytes2Uint24(Uint8ClampedList bytes, [int src = 0]) {
    return Uint32(((bytes[src] << 16) | (bytes[src + 1] << 8) | bytes[src + 2]) & 0xFFFFFFFF);
  }

  static Int32 bytes2Int32Le(Uint8ClampedList bytes, [int src = 0]) {
    int v = ((bytes[src + 3] << 24) | (bytes[src + 2] << 16) | (bytes[src + 1] << 8) | bytes[src]) & 0xFFFFFFFF;
    if (v > 0x7FFFFFFF) v = v - 0x100000000;
    return Int32(v);
  }

  static void reverse(Uint8ClampedList src) {
    for (int i = 0, length = src.length ~/ 2; i < length; i++) {
      int t = src[i];
      src[i] = src[length - i - 1];
      src[length - i - 1] = t;
    }
  }

  static bool arrayEquals(Uint8ClampedList list1, Uint8ClampedList list2, {int list1Offset = 0, int list2Offset = 0, int? list1Length, int? list2Length}) {
    list1Length ??= list1.length - list1Offset;
    list2Length ??= list2.length - list2Offset;
    if (list1Length < 0 || list2Length < 0 || list1Length != list2Length) return false;
    for (int i = 0; i < list1Length; i++) {
      if (list1[i + list1Offset] != list2[i + list2Offset]) return false;
    }
    return true;
  }

  static Uint8ClampedList buildBytes(Uint8ClampedList p1, [Uint8ClampedList? p2, Uint8ClampedList? p3, Uint8ClampedList? p4]) {
    Uint8ClampedList result = Uint8ClampedList(p1.length + (p2 == null ? 0 : p2.length) + (p3 == null ? 0 : p3.length) + (p4 == null ? 0 : p4.length));
    result.setRange(0, p1.length, p1);
    if (p2 != null) result.setRange(p1.length, p1.length + p2.length, p2);
    if (p3 != null) result.setRange(p1.length + p2!.length, p1.length + p2.length + p3.length, p3);
    if (p4 != null) result.setRange(p1.length + p2!.length + p3!.length, p1.length + p2.length + p3.length + p4.length, p4);
    return result;
  }

  static Uint8ClampedList buildByte(int p1, [int? p2, int? p3, int? p4]) {
    Uint8ClampedList result = Uint8ClampedList(1 + (p2 == null ? 0 : 1) + (p3 == null ? 0 : 1) + (p4 == null ? 0 : 1));
    result[0] = p1;
    if (p2 != null) result[1] = p2;
    if (p3 != null) result[2] = p3;
    if (p4 != null) result[3] = p4;
    return result;
  }

  static Uint8ClampedList toInt8List(dynamic p, [int length = 0]) {
    if (p is String) return Uint8ClampedList.fromList(utf8.encode(p));
    if (p is int) return num2BytesLe(p, length);
    if (p is Uint8ClampedList) return p;
    throw Exception('Unknown type ' + p.runtimeType.toString());
  }

  static bool byteEquals(int b1, int b2) {
    if (b1 < -127 || b1 > 255 || b2 < -127 || b2 > 255) return false;
    if (b1 < 0) b1 = 256 + b1;
    if (b2 < 0) b2 = 256 + b2;
    return b1 == b2;
  }

  static Int8 toInt8(int i) {
    i &= 0xFF;
    if (i >= 0x100 ~/ 2) i = i - 0x100;
    return Int8(i);
  }

  static int toInt8Int(int i) {
    i &= 0xFF;
    if (i >= 0x100 ~/ 2) i = i - 0x100;
    return i;
  }

  static Int16 toInt16(int i) {
    i &= 0xFFFF;
    if (i >= 0x10000 ~/ 2) i = i - 0x10000;
    return Int16(i);
  }

  static Int32 toInt32(int i) {
    i &= 0xFFFFFFFF;
    if (i >= 0x100000000 ~/ 2) i = i - 0x100000000;
    return Int32(i);
  }

  static Uint4 toUint4(int i) {
    i &= 0xFF;
    if (i <= -0x80 ~/ 2) i = 0x80 + i;
    return Uint4(i);
  }

  static Uint8 toUint8(int i) {
    i &= 0xFF;
    if (i <= -0x100 ~/ 2) i = 0x100 + i;
    return Uint8(i);
  }

  static Uint16 toUint16(int i) {
    i &= 0xFFFF;
    if (i <= -0x10000 ~/ 2) i = 0x10000 + i;
    return Uint16(i);
  }

  static Uint32 toUint32(int i) {
    i &= 0xFFFFFFFF;
    if (i <= -0x100000000 ~/ 2) i = 0x100000000 + i;
    return Uint32(i);
  }

  static Uint4 bitValue(int byte, int from, int length) {
    return Uint4(((byte << (8 - from - length)) & 0xFF) >> (8 - length));
  }

  static Uint4 bool2Uint4(bool b, [bool reverse = false]) {
    return Uint4((b || reverse) ? 1 : 0);
  }

  static Uint8 bool2Byte(bool b, [bool reverse = false]) {
    return Uint8((b || reverse) ? 1 : 0);
  }

  static Uint8ClampedList bool2Bytes(bool b, [bool reverse = false]) {
    Uint8ClampedList bytes = Uint8ClampedList(1);
    bytes[0] = bool2Byte(b, reverse).value;
    return bytes;
  }
}
